/*
 * Copyright (c) 2017, www.zm0618.com All Rights Reserved..
 */

package com.light.hessian.provider.discovery;

import com.ecwid.consul.v1.ConsulClient;
import com.ecwid.consul.v1.QueryParams;
import com.ecwid.consul.v1.Response;
import com.ecwid.consul.v1.health.model.HealthService;
import com.ecwid.consul.v1.kv.model.GetValue;
import com.light.hessian.consul.util.ConsulClientUtils;
import com.light.hessian.provider.config.HessianProviderInfo;
import com.light.hessian.provider.config.ProviderInfo;
import com.light.hessian.provider.config.ProviderStatus;
import com.light.hessian.provider.slot.ProviderStatusSlot;
import com.light.hessian.provider.slot.ProviderStatusSlotHolder;
import com.light.hessian.remoting.client.balance.BalancerHolder;
import com.light.hessian.remoting.client.balance.LRTBalancer;
import com.light.hessian.remoting.client.balance.RandomBalancer;
import com.light.hessian.remoting.client.balance.RoundrobinBalancer;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Created by zhaojun on 2017/02/09.
 */
public class ProviderAutoDiscoverer extends ProviderDiscovererSupport implements ApplicationContextAware,BeanDefinitionRegistryPostProcessor {

    // 一个 discoverer维护一个provider  一个provider 对应多个状态

    private volatile BalancerHolder balancerHolder=new BalancerHolder();
    private volatile ProviderStatusSlotHolder statusSlotHolder=new ProviderStatusSlotHolder();
    private volatile DiscoverBaseReference discoverReference;

    private ApplicationContext applicationContext;
    private ConsulClient consulClient;

    @Override
    protected void handleDiscoveryReference(DiscoverBaseReference discoverReference) {

        if (this.discoverReference==null){
            synchronized (ProviderAutoDiscoverer.class){

                if (this.discoverReference==null){
                    this.discoverReference=discoverReference;
                    if (discoverReference instanceof HessianLocalDiscoverReference){
                        List<ProviderInfo> providerList = discovery4Local((HessianLocalDiscoverReference) discoverReference);
                        logger.debug("-------register provider==>{}--balance ==>{}",this.getProvider(),this.getBalance());
                        registerProvider(providerList);
                        registerBalancers();
                    }
                    if (discoverReference instanceof HessianConsulDiscoverReference){
                        List<ProviderInfo> providerList =discovery4Consul((HessianConsulDiscoverReference) discoverReference);
                        logger.debug("-------register provider==>{}--balance ==>{}",this.getProvider(),this.getBalance());
                        registerProvider(providerList);
                        registerBalancers();
                    }
                    this.startRefreshSlot();
                }
            }

        }
    }

    private List<ProviderInfo> discovery4Local(HessianLocalDiscoverReference discoverReference) {
        HessianLocalDiscoverReference  localDiscoverReference= discoverReference;
        String protocol = localDiscoverReference.getProtocol();
        List<String> nodes = localDiscoverReference.getNodes();
        String pathPrefix = localDiscoverReference.getPathPrefix();
        String currentProvider = localDiscoverReference.getProvider();
        List<ProviderInfo> providerList = new ArrayList<ProviderInfo>();
        for (String node:nodes) {
            HessianProviderInfo providerInfo=new HessianProviderInfo();
            providerInfo.setPathPrefix(pathPrefix);
            providerInfo.setProtocol(protocol);
            providerInfo.setShowBalancer(discoverReference.isShowBalancer());
            providerInfo.setServiceUrl(String.format("%s://%s%s",protocol,node,pathPrefix));
            providerInfo.setProvider(currentProvider);
            providerList.add(providerInfo);
        }
        return providerList;
    }

    private void registerProvider(List<ProviderInfo> providerList) {
        this.statusSlotHolder.registerProvider(providerList);
    }

    private void registerBalancers() {
        this.balancerHolder.registerBalancer(BeanUtils.instantiate(RoundrobinBalancer.class),this.statusSlotHolder);
        this.balancerHolder.registerBalancer(BeanUtils.instantiate(RandomBalancer.class),this.statusSlotHolder);
        this.balancerHolder.registerBalancer(BeanUtils.instantiate(LRTBalancer.class),this.statusSlotHolder);
        this.balancerHolder.refreshAllBalancers();
    }

    private List<ProviderInfo> discovery4Consul(HessianConsulDiscoverReference discoverReference) {
        String provider=discoverReference.getProvider();
      /*  String transProtocol=discoverReference.getTransProctocol();*///暂时只用了一种协议 http
        List<ProviderInfo> providerList = new ArrayList<ProviderInfo>();
        this.consulClient=createConsulClient(discoverReference);
        String token=discoverReference.getToken();
        Response<List<HealthService>> response=StringUtils.isNotBlank(token)? this.consulClient.getHealthServices(provider, true, QueryParams.DEFAULT, token):this.consulClient.getHealthServices(provider, true, QueryParams.DEFAULT);
        if (response!=null){
            List<HealthService> healthServices = response.getValue();
            if (CollectionUtils.isNotEmpty(healthServices)){
                for (HealthService healthService : healthServices){
                    HealthService.Service service = healthService.getService();
                    String address=service.getAddress();
                    Integer port = service.getPort();
                    List<String> tags = service.getTags();

                    String serviceName=service.getService();
                    String serviceId=service.getId();

                    Response<GetValue> cfgAttachResp =StringUtils.isNotBlank(token)?this.consulClient.getKVValue(String.format("%s.%s.cfg.attach", serviceName, serviceId),token):this.consulClient.getKVValue(String.format("%s.%s.cfg.attach", serviceName, serviceId));

                    String serviceProtocol=null;//服务访问协议
                    String pathPrefix=null;  //服务前缀

                    if (cfgAttachResp!=null){
                        GetValue getVal = cfgAttachResp.getValue();
                        String cfgAttach = getVal.getValue();
                        if (StringUtils.isNotBlank(cfgAttach)){
                            String[] attach = StringUtils.split("@");
                            serviceProtocol=attach[1];
                            pathPrefix=attach[0];
                        }
                    }else{
                        serviceProtocol=tags.get(0);//服务访问协议
                        pathPrefix=tags.get(1);  //服务前缀
                    }
                    HessianProviderInfo providerInfo=new HessianProviderInfo();
                    providerInfo.setPathPrefix(pathPrefix);
                    providerInfo.setShowBalancer(discoverReference.isShowBalancer());
                    providerInfo.setProtocol(serviceProtocol);
                    providerInfo.setServiceUrl(String.format("%s://%s:%s%s",serviceProtocol,address,port,pathPrefix));
                    providerInfo.setProvider(provider);
                    providerList.add(providerInfo);
                }
            }
        }
        return providerList;
    }


    protected  ConsulClient  createConsulClient(HessianConsulDiscoverReference discoverReference){
        if (this.consulClient==null){
            String registryNode = selectNode(discoverReference);
            this.consulClient= ConsulClientUtils.createClient(registryNode);
        }
        return this.consulClient;
    }
    private String selectNode(HessianConsulDiscoverReference consulDiscoverReference) {
        List<String> nodes = consulDiscoverReference.getNodes();
        if (CollectionUtils.isNotEmpty(nodes)) {
            String providerNode = nodes.get(0);
            return providerNode;
        }
        return null;
    }

    private void  prepareStatusSlotPiple(BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(PreparedStatusSlotPiple.class);
        builder.addPropertyValue("statusSlotHolder",statusSlotHolder);
        builder.addPropertyValue("balancerHolder",balancerHolder);
        builder.addPropertyValue("balance",getBalance());
        builder.setScope(ConfigurableBeanFactory.SCOPE_SINGLETON);
        GenericBeanDefinition pipeBeanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
        registry.registerBeanDefinition(this.getProvider(),pipeBeanDefinition);
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }



    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.prepareStatusSlotPiple(registry);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }


    private void startRefreshSlot() {
        getPoolExecutor().execute(new Runnable() {//  刷新槽位状态
            @Override
            public void run() {
                DiscoveryType  discoveryType1=getDiscoveryType();
                if (discoveryType1==DiscoveryType.local){//本地配置  5秒钟重置线程
                    try {
                        while (true){

                            List<ProviderStatusSlot> statusSlots =  statusSlotHolder.getProviderStatusSlots();
                            if (CollectionUtils.isNotEmpty(statusSlots)){
                                for (ProviderStatusSlot statusSlot:statusSlots) {
                                    statusSlot.setStatus(ProviderStatus.UP);
                                }

                                TimeUnit.SECONDS.sleep(5);
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if (discoveryType1==DiscoveryType.consul){//consul注册中心 定时拉取
                    HessianConsulDiscoverReference disReference = (HessianConsulDiscoverReference) discoverReference;
                    int interval = disReference.getInterval();

                    try {
                        while (true){
                            List<ProviderInfo> providerList =discovery4Consul(disReference);
                            int ilen=CollectionUtils.size(providerList);
                            List<ProviderStatusSlot> statusSlots = statusSlotHolder.getProviderStatusSlots();
                            int slen=CollectionUtils.size(statusSlots);

                            if (ilen!=0){
                                if (ilen!=slen){
                                    statusSlotHolder.registerProvider(providerList);
                                    balancerHolder.refreshAllBalancers();
                                }
                            }else{
                                if (ilen>0&&ilen==slen){
                                    // 当前节点是否已经存在
                                    int tag=0;//大于0存在有不相同的节点
                                    for (int i=0;i<ilen;i++){
                                        boolean b = false;
                                        ProviderInfo providerInfo=  providerList.get(i);
                                        for (ProviderStatusSlot statusSlot:statusSlots) {
                                            if (statusSlot.getBaseProviderInfo().equals(providerInfo)){
                                                statusSlot.setStatus(ProviderStatus.UP);
                                                b=true;
                                            }
                                        }
                                        if (!b){
                                            tag++;
                                        }
                                    }
                                    if (tag>0){//如果有不相同的节点 重新注册节点
                                        statusSlotHolder.registerProvider(providerList);
                                    }
                                    balancerHolder.refreshAllBalancers();
                                }
                            }

                            TimeUnit.SECONDS.sleep(5);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        });
    }


}
